/*
 *	CODESPACE
 *	A real-time syntax highlighting javascript editor
 *
 *	Copyright (C) 2008 Francois Botman, <Franchie.brightspark@gmail.com>
 *
 *	This script is licenced under: Creative Commons Attribution-Noncommercial-Share Alike 3.0
 *	(see http://creativecommons.org/licenses/by-nc-sa/3.0/).
 *	It bascially means that it can be modified and shared, provided it is
 *	clearly attributed, and can be used in personnal/charity projects for free!
 *	(Commercial use requires prior negotiation with the author).
 *	In any case, should anything go wrong, I cannot be held responsible!
 *
 *	-----------
 *
 *	This file contains functions that handle syntax highlighting.
 *
 *	Syntax highlighting is done in the following way: it is a word-based engine, in that it is not possible to highlight individual letters of a word (other than by css trickery).
 *	It is possible to apply rules to single words ('match'), or to a block ('start' and 'stop').
 *	Rules are easily propagated fom line to line, since the state of the engine is saved for each line. This allows syntax highlighting to be
 *	both rapidly updated and propagated to the following lines (such as in the case of a block opening in the middle of the editor).
 *
 *	Code folding is implemented in a similar manner. Simply create a block rule with the css style set to "folding", and the rest will be automatic.
 *
 *	Please not that each rule can set several styles, each being seperated by a space.
 */

/*
 *	This structure will contain all the rules. 
 *	Other rules will be added in other files...
 */
Codespace.Language = {};
var char_end_of_string =	'\u0002';

/*
 *	Also define a 'default' language, in case none is specified
 */
Codespace.Language['default'] = {
	/*
	 *	Rules:
	 *	These rules determine what css class a word will have.
	 *	There are two forms: either just a word, or a word block.
	 *		- A word rule just modifies the style of the matching word
	 *		- A block rule is defined by a start and an end, and everything in between is applied the class.
	 *	The special character 'char_end_of_string' matches the end of a physical line.
	 *
	 *	Thus the format of a rule is the following:
	 *		- start:	rule that starts a block. If this is specified, there should usually be a stop
	 *		- stop:	rule that ends a block.
	 *		- match:	rule that matches a single word and is not propagated.
	 *		- css:		the name of the css style to apply (multiple styles seperated by a space).
	 */
	rules: [
		{start: null,	stop: null,								match: /\\.*/,	css: 't1'},
		{start: /\{/,	stop: /\}/,								match: null,	css: 't2 folding'},
		{start: /\[/,	stop: /\]/,								match: null,	css: 't3'},
		//{start: /\%/,	stop: new RegExp(char_end_of_string),	match: null,	css: 't5'},
		{start: /\/\*/,	stop: /\*\//,							match: null,	css: 't5'},
		{start: null,	stop: null,								match: /this/i,	css: 't4'},
		{match: /(https?\:\/\/|https?\:\/\/www\.|www\.)(.*)\.[a-z][a-z]([a-z]?)/i,css: 'url'},
		{match: new RegExp(char_end_of_string),	css: 't3'}
	]
};






/*
 *	And this is the formatter function.
 *	It should not be instanciated, it should be called as is.
 *
 *	This function takes the li element that stores past iterations of the highlighter, a list of word elements, and some options.
 *
 *	It returns true if the style has changed, and the changes need to be propagated to the following lines, false otherwise
 */
Codespace.SyntaxHighlighter = function(li, previous, words, options)
{
	/*
	 *	This object contains the state of the highlighter span per span.
	 *	It is convenient, as it automatically clones the properties of the object used to build it.
	 */
	function highlighterState(clone) {
		if(clone) for(var a in clone) if(clone[a]) this[a] = clone[a];
	}

	var state = new highlighterState(previous ? previous.state : null);
	var style = '';
	var rule = Codespace.Language[options.language] || Codespace.Language['default'];
	
	// Apply all rules
	for(var x=0; x<words.length; x++) {
		var txt = words[x].innerHTML;
		if(x==words.length-1) txt += char_end_of_string;
		
		if(!txt) continue;
		style = '';
		
		for(var y=0; y<rule.rules.length; y++) {
			// Word-matching, change only current word
			if(rule.rules[y].match && txt.match(rule.rules[y].match)) style += rule.rules[y].css + ' ';
		
			// Block matching: propagate changes:
			// Begin css block
			if(rule.rules[y].start && txt.match(rule.rules[y].start)) {
				//style += rule.rules[y].css + ' ';
				var s = rule.rules[y].css.split(' ');
				// Some rules are actually a combination of options!
				for(var q=0, l=s.length; q<l; q++) state[s[q]] = (state[s[q]]) ? (state[s[q]]+1) : 1;
			}
			// End css block
			if(rule.rules[y].stop && txt.match(rule.rules[y].stop)) {
				var s = rule.rules[y].css.split(' ');
				for(var q=0, l=s.length; q<l; q++) {
					if(state[s[q]] && state[s[q]]>0) style += s[q] + ' ';
					state[s[q]] = (state[s[q]] && state[s[q]]>1) ? (state[s[q]]-1) : null;
					if(!state[s[q]]) delete state[s[q]];
				}
			}
		}

		// And apply all remaining block styles
		for(var y in state) if(state[y]>0) style += y+' ';
	
		words[x].className = 'codespace '+style;
	}
	
	// And return whether the styles were changed compared to the given reference. Only once the changes observed is the state updated!
	if(!li.state) {li.state = state;return true;}
	for(var i in state) if(li.state[i] != state[i]) {li.state = state;return true;}
	for(var i in li.state) if(li.state[i] != state[i]) {li.state = state;return true;}
	
	// Unchanged since last pass...
	li.state = state;
	return false;
}




/*
 *	This implements the code folding.
 *	It basically consists in determining the indentation value, and placing an adequate icon when that value changes.
 *	Oh, and also managing the actual line hiding!
 */
/*Codespace.Folder = function(self, previous, row)
{
	var b4 = (previous && previous.state ? previous.state['folding'] : 0) || 0;
	var after = (self && self.state ? self.state['folding'] : 0) || 0;
	if(b4 < after) row.innerHTML =  '+';
	else if(b4 > after) row.innerHTML =  (after>0) ? '=' : '-';
	else if(after>0) row.innerHTML = '|';
	else row.innerHTML =  '';
}
*/

